﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen.Helpers;
using YAPL.Language.Core;
using YAPL.Language.Core.Types;
using YAPL.Util;
using Ast = YAPL.Language.Ast;

namespace YAPL.Language.Compiler.CodeGen.ReflectionEmitGenerator.AstGen {
	public class ReFunctionCallGenerator : ReExpressionGenerator {
		public ReFunctionCallGenerator(ReCodeGenerator codeGenerator) : base(codeGenerator) {}
		public override void Generate(Ast.Expression expr) {
			var funcCallExpr = (Ast.FunctionCallExpression) expr;
			var ilGen = Generator.Get<ILGenerator>(ReCodeGenerator.IL_GENERATOR_KEY);

			if (funcCallExpr.CallingObject == null) { //calling 'global' function

				//save null value as calling object
				ilGen.Emit(OpCodes.Ldnull);
				ilGen.Emit(OpCodes.Stloc_0);

				ilGen.EmitGetFunctionFromGCL(funcCallExpr.MethodName, ClassNames.GlobalScope);
			} else { //calling function on object

				Generator.EmitExpression(funcCallExpr.CallingObject);

				//save calling object into local variable
				ilGen.Emit(OpCodes.Stloc_0);
				ilGen.Emit(OpCodes.Ldloc_0);

				ilGen.Emit(OpCodes.Callvirt, typeof (YAPLObject).GetProperty("YAPLType").GetGetMethod());
				ilGen.EmitGetFunctionFromGCLDynamically(funcCallExpr.MethodName);
			}
			//create scope variable
			var scopeVar = ilGen.EmitCreateAndSaveScope();

			//add calling object to scope
			ilGen.EmitPrepareScopeParameter(scopeVar, YAPLFunction.SELF_PARAMETER);
			ilGen.EmitCreateSimpleType(typeof(YAPLString), funcCallExpr.MethodName);
			ilGen.Emit(OpCodes.Stloc_0);
			ilGen.EmitPrepareScopeParameter(scopeVar, YAPLFunction.NAME_PARAMETER);

			for (var i = 0; i < funcCallExpr.Arguments.Count; i++) {
				var paramExpr = funcCallExpr.Arguments[i];
				var key = YAPLFunction.UNNAMED_PARAMETER + i;
				if (paramExpr is Ast.AssignmentExpression) {
					var assignmentExpr = (Ast.AssignmentExpression) paramExpr;
					Require.HasType(assignmentExpr.Left, typeof(Ast.VariableExpression), "Left side of the assignment should be variable");
					key = ((Ast.VariableExpression)assignmentExpr.Left).Name;
					paramExpr = assignmentExpr.Right;
				}
				Generator.EmitExpression(paramExpr);
				ilGen.Emit(OpCodes.Stloc_0);
				ilGen.EmitPrepareScopeParameter(scopeVar, key);
			}
			ilGen.EmitFunctionCall(scopeVar);
		}
	}
}
